package pl.edu.mimuw.irs.core.model.design;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import pl.edu.mimuw.irs.core.model.analysis.subnet.AtomicSubnet;
import pl.edu.mimuw.irs.core.model.design.Fragmentation.Type;
import pl.edu.mimuw.irs.core.model.design.exception.InternalIRSException;
import pl.edu.mimuw.irs.core.model.design.exception.InternalIRSException.Cause;
import pl.edu.mimuw.irs.core.model.petrinet.Arc;
import pl.edu.mimuw.irs.core.model.petrinet.Net;
import pl.edu.mimuw.irs.core.model.petrinet.Place;
import pl.edu.mimuw.irs.core.model.petrinet.Transition;
import pl.edu.mimuw.irs.core.util.Converter;
import pl.edu.mimuw.irs.core.util.Position;

/**
 * @author Jakub Rauch
 * Created on: 2009-05-07
 */
public class IRSPlace extends Place implements IRSNodeInterface {
	
	// *** Attributes

	private NodeType nodeType;
	private Fragmentation fragmentation;
	private IRSNodeInterface parent;
	// Children should be in specific order - intuitive according to fragmentations.
	private List<IRSNodeInterface> children;
	// Node supports
	private final Set<Fragmentation.Type> supportedFragmentations;
	{
		supportedFragmentations = new LinkedHashSet<Type>();
		supportedFragmentations.add(Type.SEQUENCE);
		supportedFragmentations.add(Type.CONCURRENCY);
		supportedFragmentations.add(Type.ITERATION);
		supportedFragmentations.add(Type.NONE);
	}
	private boolean expanded = false;
	
	// Getters and setters
	public NodeType getNodeType() {
		if (nodeType == null) throw new InternalIRSException(Cause.UNKNOWN_NODE_TYPE);
		return nodeType;
	}
	public void setNodeType(NodeType nodeType) {
		this.nodeType = nodeType;
	}
	public Fragmentation getFragmentation() {
		if (fragmentation == null) fragmentation = new Fragmentation();
		return fragmentation;
	}
	public void setFragmentation(Fragmentation fragmentation) {
		if (fragmentation == null) fragmentation = new Fragmentation(Type.NONE);
		if (!getSupportedFragmentations().contains(fragmentation.getType())) 
			throw new InternalIRSException(Cause.UNSUPPORTED_FRAGMENTATION_TYPE);
		handleFragmentation(fragmentation);
	}
	public IRSNodeInterface getParent() {
		return parent;
	}
	public void setParent(IRSNodeInterface parent) {
		this.parent = parent;
	}
	public List<IRSNodeInterface> getChildren() {
		if (children == null) children = new ArrayList<IRSNodeInterface>();
		return children; // FIXME : powinno zwracac kopi�
	}
	public void setChildren(List<IRSNodeInterface> children, Fragmentation fragmentation) {
		removeChildren();
		for (IRSNodeInterface child : children) {
			addChild(child);
		}
		this.fragmentation = fragmentation;
	}
	public void addChild(int index, IRSNodeInterface node) {
		this.getChildren().add(index, node);
		node.setParent(this);
	}
	public void addChild(IRSNodeInterface node) {
		this.getChildren().add(node);
		node.setParent(this);
	}
	public void removeChild(IRSNodeInterface node) {
		this.getChildren().remove(node);
		node.destroy();
	}
	@Override
	public boolean isRoot() {
		return getParent() == null;
	}
	public boolean isAtomic() {
		return this.getFragmentation().getType().equals(Type.NONE);
	}
	public boolean isInPlace() {
		return getIncomingArcs().size() == 0;
	}
	public boolean isOutPlace() {
		return getOutgoingArcs().size() == 0;
	}
	public Set<Type> getSupportedFragmentations() {
		if (!isInPlace() && !isOutPlace()) return supportedFragmentations;
		else {
			Set<Type> tmpFragmentations = new LinkedHashSet<Type>();
			tmpFragmentations.add(Type.SEQUENCE);
			tmpFragmentations.add(Type.NONE);
			return tmpFragmentations;
		}
	}
	public boolean isExpanded() {
		return expanded && !getFragmentation().getType().equals(Type.NONE);
	}
	public void setExpanded(boolean expand) {
		this.expanded = expand;
	}
	@Override
	public void addArc(Arc arc) {
		IRSArc irsArc = Converter.asIRSArc(arc);
		IRSNodeInterface irsSource = Converter.asIRSNodeInterface(irsArc.getSource());
		IRSNodeInterface irsTarget = Converter.asIRSNodeInterface(irsArc.getTarget());
		
		super.addArc(irsArc);
		
		for (IRSNodeInterface child : getInputChildren()) {
			if (irsSource == this) irsArc.copy(child, irsTarget);
		}
		for (IRSNodeInterface child : getInputChildren()) {
			if (irsTarget == this) irsArc.copy(irsSource, child);
		}
	}
	
	// *** Constructors
	public IRSPlace(String id, NodeType nodeType, Fragmentation fragementation, Position position, int tokens) {
		super(id, position, tokens);
		setNodeType(nodeType);
		setFragmentation(fragementation);
	}
	public IRSPlace(String id, NodeType nodeType, Position position, int tokens) {
		this(id, nodeType, null, position, tokens);
	}
	public IRSPlace(String id, Position position, int tokens) {
		this(id, NodeType.PLACE, position, tokens);
	}
	public IRSPlace(String id, Position position) {
		this(id, position, 0);
	}
	public IRSPlace(Position position) {
		this(getNewId(), position);
	}
	public IRSPlace(String id) {
		this(id, new Position());
	}
	public IRSPlace() {
		this((String)null);
	}
	
	// *** Methods
	
	// Invoked on fragmentation change - causes node structural change.
	private void handleFragmentation(Fragmentation fragmentation) {
		switch (fragmentation.getType()) {
			case NONE:
				removeChildren();
				break;
			case SEQUENCE:
				setSequence(fragmentation);
				break;
			case CONCURRENCY:
				setConcurrency(fragmentation);
				break;
			case ITERATION:
				setIteration(fragmentation);
				break;
			default:
				throw new InternalIRSException(Cause.UNSUPPORTED_FRAGMENTATION_TYPE);
		}
		
	}

	// Fragmentation private methods

	private void setSequence(Fragmentation fragmentation) {
		Fragmentation.Type parentFragmentation;
		if (isRoot()) parentFragmentation = Type.NONE;
		else parentFragmentation = getParent().getFragmentation().getType();
		/*
		switch (parentFragmentation) {
			case SEQUENCE:
				int index = getParent().getChildren().indexOf(this);
				for (IRSNodeInterface node : createSequence(fragmentation)) {
					getParent().addChild(index++, node);
				}
				this.fragmentation = new Fragmentation(Type.NONE);
				this.destroy();
				break;
			default:
				// TODO : inteligentne obs�u�enie r�nych przypadk�w fragmentacji elementu
				removeChildren();
				for (IRSNodeInterface node : createSequence(fragmentation)) {
					addChild(node);
				}
				this.fragmentation = fragmentation;
				break;
		}*/
		if (Type.SEQUENCE.equals(parentFragmentation)) {
			int index = getParent().getChildren().indexOf(this);
			for (IRSNodeInterface node : createSequence(fragmentation)) {
				getParent().addChild(index++, node);
			}
			this.fragmentation = new Fragmentation(Type.NONE);
			this.destroy();
		} else {
			// TODO : inteligentne obs�u�enie r�nych przypadk�w fragmentacji elementu
			//removeChildren();
			setChildren(createSequence(fragmentation), fragmentation);
		}
	}

	private List<IRSNodeInterface> createSequence(Fragmentation fragmentation) {
		List<IRSNodeInterface> sequence = new LinkedList<IRSNodeInterface>();
		Net net = getNet();
		
		if (fragmentation.getModifier() > 0) { // FIXME : to nie zadzia�a dla modifier = 0
			IRSPlace firstPlace = Converter.asIRSPlace(copy());
			cloneIncomingArcsTo(firstPlace);
			IRSPlace lastPlace = Converter.asIRSPlace(copy());
			cloneOutgoingArcsTo(lastPlace);
			IRSTransition lastTransition = new IRSTransition(this.getPosition());
			net.addTransition(lastTransition);
			sequence.add(firstPlace);
			sequence.add(lastTransition);
			net.addArc(new IRSArc(firstPlace, lastTransition));
			for (int i = 1; i < fragmentation.getModifier(); i++) {
				IRSPlace newPlace = new IRSPlace(this.getPosition());
				net.addPlace(newPlace);
				IRSTransition newTransition = new IRSTransition(this.getPosition());
				net.addTransition(newTransition);
				sequence.add(newPlace);
				sequence.add(newTransition);
				net.addArc(new IRSArc(lastTransition, newPlace));
				net.addArc(new IRSArc(newPlace, newTransition));
				lastTransition = newTransition;
			}
			sequence.add(lastPlace);
			net.addArc(new IRSArc(lastTransition, lastPlace));
		}
		
		return sequence;
	}
	
	private void setConcurrency(Fragmentation fragmentation) {
		List<IRSNodeInterface> nodes = new ArrayList<IRSNodeInterface>();
		for (int i = 0; i < 1 + fragmentation.getModifier(); i++) {
			IRSPlace newPlace = Converter.asIRSPlace(copy());
			cloneIncomingArcsTo(newPlace);
			cloneOutgoingArcsTo(newPlace);
			nodes.add(newPlace);
		}
		setChildren(nodes, fragmentation);
	}

	private void setIteration(Fragmentation fragmentation) {
		Net net = getNet();
		
		if (fragmentation.getModifier() > 0) {
			IRSPlace newPlace = Converter.asIRSPlace(copy());
			removeChildren();
			cloneIncomingArcsTo(newPlace);
			cloneOutgoingArcsTo(newPlace);
			IRSTransition newTransition = new IRSTransition(getPosition());
			net.addTransition(newTransition);
			net.addArc(new IRSArc(newPlace, newTransition));
			net.addArc(new IRSArc(newTransition, newPlace));
			addChild(newPlace);
			addChild(newTransition);
			if (fragmentation.getModifier() > 1) {
				newTransition.setFragmentation(new Fragmentation(Type.ALTERNATIVE, 
						fragmentation.getModifier()));
			}
		}
		this.fragmentation = fragmentation;
	}
	
	public List<IRSNodeInterface> getAtomicNodes() {
		List<IRSNodeInterface> leafs = new LinkedList<IRSNodeInterface>();
		if (getFragmentation().getType().equals(Type.NONE)) {
			leafs.add(this);
		} else {
			for (IRSNodeInterface node : getChildren()) {
				IRSNodeInterface rcpNode = (IRSNodeInterface)node;
				leafs.addAll(rcpNode.getAtomicNodes());
			}
		}
		return leafs;
	}

	public List<IRSNodeInterface> getExpandedNodes() {
		List<IRSNodeInterface> leafs = new LinkedList<IRSNodeInterface>();
		if (!isExpanded()) {
			leafs.add(this);
			return leafs;
		} else {
			for (IRSNodeInterface node : getChildren()) {
				IRSNodeInterface rcpNode = (IRSNodeInterface)node;
				leafs.addAll(rcpNode.getExpandedNodes());
			}
		}
		return leafs;
	}	
	
	public AtomicSubnet getAtomicWorkflow() {
		return getAtomicWorkflow(new AtomicSubnet());
	}
	
	public AtomicSubnet getAtomicWorkflow(AtomicSubnet atomicSubnet) {
		if (Type.NONE.equals(getFragmentation().getType())) {
			// TODO : wiecej szczeg��w miejsca
			Place place = new Place(getPrefixedNewId(this.getId()), getPosition(), getTokens());
			atomicSubnet.addPlace(place);
			atomicSubnet.getPlacesMap().put(this, place);
			Map<IRSTransition, Transition> transitionsMap = atomicSubnet.getTransitionsMap();
			for (IRSArc arc : getAtomicIncomingArcs()) {
				Transition transition = transitionsMap.get(Converter.asIRSTransition(arc.getTransition()));
				if (transition != null) {
					atomicSubnet.addArc(transition, place, arc.getWeight());
				}
			}
			for (IRSArc arc : getAtomicOutgoingArcs()) {
				Transition transition = transitionsMap.get(Converter.asIRSTransition(arc.getTransition()));
				if (transition != null) {
					atomicSubnet.addArc(place, transition, arc.getWeight());
				}
			}
		} else {
			for (IRSNodeInterface node : getChildren()) {
				atomicSubnet = node.getAtomicWorkflow(atomicSubnet);
			}
		}
		return atomicSubnet;
	}
	
	@Override
	public List<IRSArc> getAtomicIncomingArcs() {
		List<IRSArc> atomicArcs = new LinkedList<IRSArc>();
		for (Arc arc : getIncomingArcs()) {
			IRSArc irsArc = Converter.asIRSArc(arc);
			IRSTransition transition = Converter.asIRSTransition(irsArc.getTransition());
			if (transition.isAtomic()) atomicArcs.add(irsArc);
		}
		return atomicArcs;
	}

	@Override
	public List<IRSArc> getAtomicOutgoingArcs() {
		List<IRSArc> atomicArcs = new LinkedList<IRSArc>();
		for (Arc arc : getOutgoingArcs()) {
			IRSArc irsArc = Converter.asIRSArc(arc);
			IRSTransition transition = Converter.asIRSTransition(irsArc.getTransition());
			if (transition.isAtomic()) atomicArcs.add(irsArc);
		}
		return atomicArcs;
	}
	
	public IRSNodeInterface copy() {
		IRSPlace clone = new IRSPlace(getNewId(), getNodeType(), getPosition().copy(), getTokens());
		copyContentsTo(clone);
		if (getNet() != null) getNet().addPlace(clone);
		return clone;
	}
	
	public IRSResourceSet copyAsResourceSet() {
		IRSResourceSet clone = new IRSResourceSet(getNewId(), getNodeType(), getPosition().copy(), getTokens());
		copyContentsTo(clone);
		if (getNet() != null) getNet().addPlace(clone);
		return clone;
	}
	
	public IRSNodeInterface copyContentsTo(IRSNodeInterface node) {
		List<IRSNodeInterface> children = getChildren();
		List<IRSNodeInterface> clonedChildren = new LinkedList<IRSNodeInterface>();
		for (IRSNodeInterface child : children) {
			clonedChildren.add(child.copy());
		}
		
		for (int i = 0; i < children.size(); i++) {
			for (Arc arc : children.get(i).getIncomingArcs()) {
				int pos = children.indexOf(arc.getSource());
				if (pos >= 0) 
					((IRSArc)arc).copy(clonedChildren.get(pos), clonedChildren.get(i));
			}
			for (Arc arc : children.get(i).getOutgoingArcs()) {
				int pos = children.indexOf(arc.getTarget());
				if (pos >= 0) 
					((IRSArc)arc).copy(clonedChildren.get(i), clonedChildren.get(pos));
			}
		}
		
		node.setChildren(clonedChildren, getFragmentation().copy());
		
		return node;
	}
	
	protected void cloneIncomingArcsTo(IRSPlace place) {
		for (Arc arc : getIncomingArcs()) {
			IRSArc irsArc = Converter.asIRSArc(arc);
			irsArc.copy(Converter.asIRSTransition(irsArc.getTransition()), place);
		}
	}

	protected void cloneOutgoingArcsTo(IRSPlace place) {
		for (Arc arc : getOutgoingArcs()) {
			IRSArc irsArc = Converter.asIRSArc(arc);
			irsArc.copy(place, Converter.asIRSTransition(irsArc.getTransition()));
		}
	}
	
	public void destroy() {
		IRSNodeInterface parent = getParent();
		setParent(null);
		super.destroy();
		if (parent != null) parent.removeChild(this);
		removeChildren();
	}
	
	@Override
	public List<IRSNodeInterface> getInputChildren() {
		switch (getFragmentation().getType()) {
		case NONE:
			return new LinkedList<IRSNodeInterface>();
		case SEQUENCE:
			return Collections.singletonList(getChildren().get(0));
		case CONCURRENCY:
			LinkedList<IRSNodeInterface> list = new LinkedList<IRSNodeInterface>();
			for (IRSNodeInterface child : getChildren()) {
				list.add(child);
			}
			return list;
		case ITERATION:
			return Collections.singletonList(getChildren().get(0));
		default:
			throw new InternalIRSException(Cause.UNSUPPORTED_FRAGMENTATION_TYPE);
		}
	}
	
	@Override
	public List<IRSNodeInterface> getOutputChildren() {
		switch (getFragmentation().getType()) {
		case NONE:
			return new LinkedList<IRSNodeInterface>();
		case SEQUENCE:
			return Collections.singletonList(getChildren().get(getChildren().size() - 1));
		case CONCURRENCY:
			LinkedList<IRSNodeInterface> list = new LinkedList<IRSNodeInterface>();
			for (IRSNodeInterface child : getChildren()) {
				list.add(child);
			}
			return list;
		case ITERATION:
			return Collections.singletonList(getChildren().get(0));
		default:
			throw new InternalIRSException(Cause.UNSUPPORTED_FRAGMENTATION_TYPE);
		}
	}
	
	private void removeChildren() {
		List<IRSNodeInterface> list = new LinkedList<IRSNodeInterface>();
		for (IRSNodeInterface node : getChildren()) {
			if (node != this) list.add(node);
		}
		for (IRSNodeInterface node : list) {
			node.destroy();
		}
		this.fragmentation = new Fragmentation(Type.NONE);
	}
}
